import { getRun, listEvents } from "@autoscholar/core"; import { redact } from "@autoscholar/observability"; import { STAGES } from "@autoscholar/schemas"; import Link from "next/link"; import { notFound } from "next/navigation"; import { StatusBadge } from "@/components/StatusBadge"; import { disciplineLabel, fmtBytes, fmtDateTime, fmtUsd, modeLabel, shortHash } from "@/lib/format"; import { db } from "@/lib/server/db"; import { LiveTimeline } from "./LiveTimeline"; import { RunActions } from "./RunActions"; export const dynamic = "force-dynamic"; export default async function RunDetail({ params }: { params: Promise<{ id: string }> }) { const { id } = await params; const d = db(); let data: Awaited>; try { data = await getRun(d, id); } catch { notFound(); } const { run, attempts, stages, artifacts, lease, gates } = data; const initialEvents = (await listEvents(d, id, 0, 500)).map((e) => redact({ seq: e.seq, at: e.at.toISOString(), level: e.level, kind: e.kind, stage: e.stage, message: e.message, data: e.data, }), ); const latestAttempt = attempts.at(-1); const stageMap = new Map(stages.filter((s) => s.attemptId === latestAttempt?.id).map((s) => [s.stage, s])); const latestArtifacts = new Map(); for (const a of artifacts) if (!latestArtifacts.has(a.path)) latestArtifacts.set(a.path, a); const manifest = run.manifest as Record; return (

Runs

{run.title}

{disciplineLabel(run.discipline)} · {modeLabel(run.researchMode)} {run.secondaryModes.length ? ` (+${run.secondaryModes.map(modeLabel).join(", ")})` : ""} {run.id}

{[ ["Attempt", `${run.currentAttemptNo}/${run.maxAttempts}`], ["Stage", run.currentStage ?? "—"], ["Cost", `${fmtUsd(run.totalCostUsd)} / ${fmtUsd(run.maxBudgetUsd)}`], ["Runtime cap", `${run.maxRuntimeMinutes} min`], ["Worker", lease ? `lease #${lease.fencingToken}` : latestAttempt?.workerId ? "released" : "—"], ["Auto-publish", run.autoPublish ? "yes" : "no"], ].map(([l, v]) => (

{l}

{v}

))}

Stage progress

    {STAGES.map((s) => { const ex = stageMap.get(s.key); const st = ex?.status ?? (s.key === "quality" && gates.length ? "completed" : s.key === "publication" && run.status === "published" ? "completed" : "pending"); return (
  1. {s.index} {s.name}
  2. ); })}

Timeline

Artifacts ({latestArtifacts.size})

{latestArtifacts.size === 0 ? (

No artifacts uploaded yet.

) : (
    {[...latestArtifacts.values()].map((a) => (
  • {a.path} v{a.version} · {fmtBytes(a.bytes)} · {shortHash(a.sha256, 8)} {a.synthetic ? " · synthetic" : ""}
  • ))}
)}

Attempts

    {attempts.map((a) => (
  • #{a.attemptNo} {" "} {a.resumeFromStage ? `resumed after ${a.resumeFromStage}` : ""} {fmtDateTime(a.startedAt)} → {a.endedAt ? fmtDateTime(a.endedAt) : "…"} ·{" "} {fmtUsd(a.costUsd)} {a.error && ( {a.error} )}
  • ))}

Immutable manifest

{Object.entries(manifest).map(([k, v]) => (
{k}
{typeof v === "string" ? v : JSON.stringify(v)}
))}
manifestSha256
{run.manifestSha256}
{gates.length > 0 && (

Latest quality evaluation

{gates.filter((g) => g.status === "pass").length} pass ·{" "} {gates.filter((g) => g.status === "fail").length} fail ·{" "} {gates.filter((g) => g.status === "warn").length} warn

Open quality report
)}
); }